/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.iec.edp.caf.msu.common.loader;


import io.iec.edp.caf.app.manager.classloader.CAFClassLoader;
import io.iec.edp.caf.msu.common.manager.AppManager;
import io.iec.edp.caf.multicontext.classloader.PlatformClassloader;
import lombok.extern.slf4j.Slf4j;
import sun.misc.Resource;
import sun.misc.URLClassPath;

import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * 类加载器预热器
 *
 * @author Leon Huo
 */
@Slf4j

public class ClassPreloader {
    private final String suName;
    //名称过滤器
    private ClassNameFilter classNameFilter;

    //包名前缀
    private final String[] packages;

    //URLClasspath
    private final URLClassPath urlClassPath;

    //需处理的类名
    private final List<String> classNames = new ArrayList<>();

    //类加载器
    private final ClassLoader classLoader;

    /**
     * 构造方法 需传入指定的SU名称以及要扫描的包名
     * <p>
     * todo classloader需使用内部方法拿到
     *
     * @param suName
     * @param packages
     */
    public ClassPreloader(String suName, String... packages) {
        this.packages = packages;
        this.suName = suName.toLowerCase();
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

        boolean ifParallel = System.getProperty("parallel.startup", "false").equals("true");
        //非并行启动
        if(!ifParallel){
            this.urlClassPath = ((CAFClassLoader)classLoader).getSuClasspath(this.suName);
            this.classLoader = classLoader;
        }else{
            //并行启动
            this.urlClassPath = ((PlatformClassloader)classLoader).getSuClasspath(this.suName);
            this.classLoader = classLoader.getParent();
        }
        //this.classLoader = AppManager.getCafClassLoader();
        //this.urlClassPath = AppManager.getCafClassLoader().getSuClasspath(this.suName);
    }

    /**
     * 支持传入一个类名称过滤器
     *
     * @param suName
     * @param classNameFilter
     * @param packages
     */
    public ClassPreloader(String suName, ClassNameFilter classNameFilter, String... packages) {
        this.classNameFilter = classNameFilter;
        this.packages = packages;
        this.suName = suName.toLowerCase();
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        boolean ifParallel = System.getProperty("parallel.startup", "false").equals("true");
        //非并行启动
        if(!ifParallel){
            this.urlClassPath = ((CAFClassLoader)classLoader).getSuClasspath(this.suName);
            this.classLoader = classLoader;
        }else {
            this.urlClassPath = ((PlatformClassloader)classLoader).getSuClasspath(this.suName);
            this.classLoader = classLoader.getParent();
        }
//        this.classLoader = AppManager.getCafClassLoader();
//        this.urlClassPath = AppManager.getCafClassLoader().getSuClasspath(this.suName);
    }

    private void init() {
        long startTime = System.currentTimeMillis();

        for (String nowPackage : packages) {
            String nowPackageDir = nowPackage.replace('.', '/');
            try {
                Enumeration<Resource> resources = this.urlClassPath.getResources(nowPackageDir);

                while (resources.hasMoreElements()) {
                    URL url = resources.nextElement().getURL();

                    // 获取jar
                    String protocol = url.getProtocol();
                    if ("jar".equals(protocol)) {
                        JarFile jar = ((JarURLConnection) url.openConnection()).getJarFile();
                        //扫描jar包文件 并添加到集合中
                        classNames.addAll(findClassesByJar(nowPackage, jar));
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        long timeCost = System.currentTimeMillis() - startTime;
        log.info("ClassPreLoader initialized in " + timeCost + "ms.");
    }

    private List<String> findClassesByJar(String pkgName, JarFile jar) {
        List<String> result = new ArrayList<>();
        String pkgDir = pkgName.replace(".", "/");

        Enumeration<JarEntry> entry = jar.entries();

        JarEntry jarEntry;
        String name, className;
        //遍历JAR包
        while (entry.hasMoreElements()) {
            jarEntry = entry.nextElement();
            name = jarEntry.getName();
            // 如果是以/开头的 获取后面的字符串
            if (name.charAt(0) == '/') {
                name = name.substring(1);
            }

            if (jarEntry.isDirectory() || !name.startsWith(pkgDir) || !name.endsWith(".class")) {
                continue;
            }

            //匹配到class 转换真正的类名
            className = name.substring(0, name.length() - 6).replace('/', '.');
            if (classNameFilter == null || classNameFilter.doFilter(className)) {
                result.add(className);
            }
        }

        return result;
    }

    public void startPreload() {
        Thread thread = new Thread(() -> {
            if (urlClassPath != null) {
                init();
            }

            long startTime = System.currentTimeMillis();
            int count = 0;
            for (String s : classNames) {
                try {
                    Class.forName(s, false, this.classLoader);
                    Thread.yield();
                    count++;
                }catch (NoClassDefFoundError error){
                    error.printStackTrace();
                }catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }

            long timeCost = System.currentTimeMillis() - startTime;
            log.info("Service Unit: " + suName + " " + count + " class loaded in " + timeCost + "ms.");
        });

        thread.start();
    }

}
